﻿using Easy.Common;
using Easy.Domain.Core.Specification;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Easy.Domain.Core
{
    public class Repository<T> : IRepository<T>, IDisposable
        where T : class
    {
        #region Constructor

        private IUnitOfWork _unitOfWork;

        public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }

        public Repository(IUnitOfWork unitOfWork)
        {
            CheckHelper.NotNull(unitOfWork, "unitOfWork");

            _unitOfWork = unitOfWork;
        }

        #endregion

        #region Command

        public void Add(T item)
        {
            CheckHelper.NotNull(item, "item");

            _unitOfWork.Set<T>().Add(item);
        }

        /// <summary>
        /// 标记该实体的状态为 'Modified'。
        /// 注意：<para>
        /// 1. 标记的只是关于该实体，并不会影响到导航属性，非导航属性都会被更新，导航属性的更新则根据EF的自动跟踪来完成 </para>
        /// 2. 例如主键 Id = 1 的实体A已经存在上下文，新创建一个 Id = 1 的实体B，
        /// 当把B的状态修改为 'Modified' 时会出现异常，因为主键冲突，处理这种情况可以使用覆盖方法，Cover(A, B);
        /// </summary>
        public void Update(T item)
        {
            CheckHelper.NotNull(item, "item");

            _unitOfWork.SetModified(item);
        }

        /// <summary>
        /// 修改实体部分属性
        /// </summary>
        /// <param name="item">实体</param>
        /// <param name="updatePropertys">需要修改的属性。注意：参数object为匿名对象 new{item.Propert1, item.Propert2, ..}</param>
        public void Update(T item, Expression<Func<T, object>> updatePropertys)
        {
            CheckHelper.NotNull(item, "item");

            var entry = _unitOfWork.Entry(item);

            //如果整个实体已经标记为修改，不再做处理
            if (entry.State == EntityState.Modified)
            {
                return;
            }


            //根据传入的 Expression<Func<T, object>> 转换为属性名称
            List<string> updatePtyNamelst = updatePropertys.ToPropertyNames();


            if (updatePtyNamelst.Count <= 0)
            {
                return;
            }

            entry.State = EntityState.Unchanged;

            foreach (string ptyName in updatePtyNamelst)
            {
                entry.Property(ptyName).IsModified = true;
            }
        }

        public void Delete(T item)
        {
            CheckHelper.NotNull(item, "item");

            _unitOfWork.Entry(item).State = EntityState.Deleted;
        }

        public void Delete(Guid id)
        {
            T deleteItem = Find(id);

            _unitOfWork.Entry(deleteItem).State = EntityState.Deleted;
        }

        /// <summary>
        /// 标记该实体的状态为 'Unchanged'。
        /// 注意：例如主键 Id = 1 的实体A已经存在上下文，新创建一个 Id = 1 的实体B，
        /// 当把B的状态修改为 'Unchanged' 时会出现异常，因为主键冲突，处理这种情况可以使用覆盖方法，Cover(A, B);
        /// </summary>
        public void Attach(T item)
        {
            CheckHelper.NotNull(item, "item");

            _unitOfWork.Attach(item);
        }

        public void Detach(T item)
        {
            CheckHelper.NotNull(item, "item");

            _unitOfWork.Detach(item);
        }

        /// <summary>
        /// 覆盖存在于上下文实体 toOriginal 的当前值。注意：如果实体 toOriginal 尚未存在于上下文中，那么会出现异常。
        /// </summary>
        /// <param name="toOriginal">即将被覆盖的对象</param>
        /// <param name="formCurrent">数据来源</param>
        public void Cover(T toOriginal, T formCurrent)
        {
            CheckHelper.NotNull(toOriginal, "toOriginal");
            CheckHelper.NotNull(formCurrent, "formCurrent");

            _unitOfWork.ApplyCurrentValues(toOriginal, formCurrent);
        }

        #endregion

        #region Query

        /// <summary>
        /// 先会在上下文中查找，即使这些数据还没有保存到数据库，如果找到就返回结果；<para>
        /// 如果找不到才会到数据库中查找，找到之后会在上下文中缓存起来。</para>
        /// </summary>
        /// <param name="id">主键</param>
        public T Find(Guid id)
        {
            return _unitOfWork.Set<T>().Find(id);
        }

        public T Find(ISpecification<T> specification, bool isNoTracking = false)
        {
            CheckHelper.NotNull(specification, "specification");

            if (isNoTracking)
            {
                return _unitOfWork.Set<T>().Where(specification.SatisfiedBy()).AsNoTracking().FirstOrDefault();
            }
            else
            {
                return _unitOfWork.Set<T>().Where(specification.SatisfiedBy()).FirstOrDefault();
            }
        }

        public IQueryable<T> Find(Expression<Func<T, bool>> filter, bool isNoTracking = false)
        {
            CheckHelper.NotNull(filter, "filter");

            if (isNoTracking)
            {
                return _unitOfWork.Set<T>().Where(filter).AsNoTracking();
            }
            else
            {
                return _unitOfWork.Set<T>().Where(filter);
            }
        }

        public IQueryable<T> Matching(ISpecification<T> specification, bool isNoTracking = false)
        {
            CheckHelper.NotNull(specification, "specification");

            if (isNoTracking)
            {
                return _unitOfWork.Set<T>().Where(specification.SatisfiedBy()).AsNoTracking();
            }
            else
            {
                return _unitOfWork.Set<T>().Where(specification.SatisfiedBy());
            }
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            if (_unitOfWork != null)
            {
                _unitOfWork.Dispose();
            }
        }

        #endregion

    }
}
